/*
 * Copyright 2009 Jiwei Xu (http://www.xujiwei.com)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

/**
 * require a class or package
 * @param {String} path path to the class or package
 */
var require, provide;
var TinyJS = TinyJS || {};
(function() {
    var loaded = {};
    var xhr = new XMLHttpRequest();

    var loadingQueue = [],
        loadedQueue  = [];

    var _require = function(path, reload) {
        if (reload || !loaded[path]) {
            console.log("loading " + path + " ...");

            loaded[path] = true;
            loadingQueue.push(path);
            var url = path.replace(/\./g, "/") + ".js";
            xhr.open("GET", url, false);
            xhr.send("");
            try {
                if (xhr.status === 200 || xhr.status === 304 || xhr.status === 0) {
                    eval(xhr.responseText);
                    
                    var ns = window,
                        parts = path.split(".");

                    while (parts.length > 1) {
                        var part = parts.shift();
                        if (!ns[part]) {
                            ns[part] = {};
                        }
                        ns = ns[part];
                    }

                    ns[parts[0]] = window[parts[0]];

                    loadedQueue.push(loadingQueue.pop());
                } else {
                    throw new Error("file " + url + " can't be found!");
                }
            } catch(e) {
                document.write("Error in loading " + path + ":<br />");
                document.write(e.message);
                throw e;
            }
        }
    };
    _require.loadingSequence = loadedQueue;
    
    require =  _require;

    provide = function(path) {
        loaded[path] = true;
        path = path.lastIndexOf(".") == -1 ? path : path.substr(path.lastIndexOf(".") + 1);
        window[path] = null;

        var parts = path.split(".");
        var ns = window;
        while (parts.length > 0) {
            var part = parts.shift();
            if (!ns[part]) {
                ns[part] = {};
            }
            ns = ns[part];
        }
    };
})();

/**
 * simple class model
 * @param {Object} base optional, base class
 * @param {Object} def  class definition
 * @sample
 * Person = Class({
 *   Hi : function() {
 *   }
 * });
 * Bill = Class(Person, {
 *   Hi : function() {
 *   }
 * });
 */
var Class = (function() {

    var Base = function() {
        var Caller = arguments.callee.caller;
        Caller && Caller.prototype && Caller.prototype.__inherit__ &&
            Caller.prototype.__inherit__.apply(this, arguments);
    };

    var copy = function(dest, src) {
        for (var p in src) {
            if (src.hasOwnProperty(p) && dest[p] === undefined) {
                dest[p] = src[p];
            }
        }
    };

    return function() {
        var args = Array.prototype.slice.call(arguments);

        if (args.length < 1) {
            throw new Error("No class definition!");
        }

        if (args.length > 2) {
            throw new Error("Too many parent class!");
        }

        var func, def = args.pop();

        if (def["Initialize"]) {
            func = def["Initialize"];
            delete def["Initialize"];
        } else {
            func = function() {
                this.Base.apply(this, arguments);
            };
        }
        func.prototype.Base = Base;

        if (def["static"]) {
            var statics = def["static"];
            delete def["static"];
            copy(func, statics);
        }

        copy(func.prototype, def);

        if (args.length > 0) {
            var base = args.pop();
            func.prototype.__inherit__ = base;
            copy(func, base);
            copy(func.prototype, base.prototype);
        }

        return func;
    };
})();

/**
 * map message
 */
var declareMessage = function(obj, msg, handle) {
    var objId = "";
    if (typeof obj === "string") {
        objId = obj;
        obj = document.getElementById(obj);
    }

    ASSERT_VALID(obj, "Declare messages on a non-exist object '" + objId + "'");

    var msgs = msg;
    if (handle) {
        msgs = {};
        msgs[msg] = handle;
    }

    for (var p in msgs) {
        if (msgs.hasOwnProperty(p)) {
            try {
                obj.addEventListener(p, msgs[p], false);
                //console.info("add '%s' event listener to %o", p, obj);
            } catch(e) {
                console.warn("%o add '%s' event listener to %o failed", arguments.callee.caller, p, obj);
            }
        }
    }
};

var ASSERT_VALID = function(obj, msg, caller) {
    if (window._DEBUG && (obj === false || obj === null || obj === undefined)) {
        msg = msg || "Assert Valid Failed!";
        if (window.console) {
            console.assert(false, msg, caller);
        } else {
            throw new Error(msg);
        }
    }
};

var EventHandler = Class({
    Initialize : function(name) {
        this.subscribers = [];
    },

    Subscribe : function(subscriber, scope) {
        this.subscribers.push([subscriber, scope]);
    },

    Fire : function() {
        var scope, args = Array.prototype.slice(arguments);
        for (var i = 0; i < this.subscribers.length; ++i) {
            var subscriber = this.subscribers[i];
            subscriber[0].apply(subscriber[1] || this, args);
        }
    }
});

var TinyMQ = (function() {
    var msgBus = {
    };
    
    var msgQueue = {
    };
    
    return {
        Subscribe : function(msg, func, scope) {
            if (!msgBus[msg]) {
                msgBus[msg] = [];
            }
            
            msgBus[msg].push([func, scope || window]);
        },
        
        Publish : function(msg) {
            if (msgBus[msg]) {
                var args = Array.prototype.slice.call(arguments, 1);
                var subscribers = msgBus[msg];
                setTimeout(function() {
                    for (var i = 0; i < subscribers.length; ++i) {
                        var subscriber = subscribers[i];
                        try {
                            subscriber[0].apply(subscriber[1], args);
                        } catch (e) {
                            console.warn("%s %o %o", msg, subscriber[0], subscriber[1]);
                            console.warn(e.message);
                        }
                    }
                }, 0);
            }
        },
        
        Push : function(msg, arg) {
            if (!msgQueue[msg]) {
                msgQueue[msg] = [];
            }
            msgQueue[msg].push(arg);
        },
        
        Pick : function(msg) {
            if (msgQueue[msg].length > 0) {
                return msgQueue[msg].shift();
            }
            
            return null;
        }
    };
})();